﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.Generic;
using SuperDeepZoom;
using System.Collections.ObjectModel;

namespace SuperDeepZoom
{
    /// <summary>
    /// Shows and updates the FlyThrough overlay (as the MSI changes)
    /// </summary>
    internal class FlyThroughOverlayManager
    {
        FlyThroughManager manager;
        MultiScaleImage msi;
        ImageArranger arranger;

        /// <summary>
        /// Contains the fly through segments. A segment is a bit like a mini ViewModel.
        /// </summary>
        ObservableCollection<FlyThroughOverlaySegment> segments = new ObservableCollection<FlyThroughOverlaySegment>();

        List<FlyThroughData> filteredData = new List<FlyThroughData>();

        ObservableCollection<FlyThroughData> sourceList;

        public FlyThroughOverlayManager(FlyThroughManager manager, MultiScaleImage msi, ImageArranger arranger)
        {
            this.msi = msi;
            this.arranger = arranger;
            this.manager = manager;            
            
            sourceList = manager.UrlFlyThroughData;
            
            init();

            msi.ViewportChanged += new RoutedEventHandler(msi_ViewportChanged);            
            arranger.ArrangeAnimationComplete += new EventHandler(arranger_ArrangeAnimationComplete);
        }

        /// <summary>
        /// Ensure the list of images is sychronised with the filter state of the MSI
        /// i.e. don't show images that are hidden due to filtering.
        /// </summary>
        public List<MultiScaleSubImage> CurrentFlyThoughImages
        {
            get
            {
                if (sourceList != null)
                {
                    List<MultiScaleSubImage> result = new List<MultiScaleSubImage>();
                    foreach (var d in sourceList)
                    {
                        var subImage = msi.SubImages[d.ImageId];
                        if (arranger.ImagesToShow.Contains(subImage))
                        {
                            result.Add(subImage);
                        }
                    }
                    return result;
                }
                else
                {
                    return null;
                }
            }
        }

        /// <summary>
        /// Changes between a URL based fly through to the current fly through recorded from user interaction
        /// </summary>
        /// <param name="source"></param>
        public void SwitchOverlaySource(FlyThroughSource source)
        {
            if (sourceList != null)
            {
                sourceList.CollectionChanged -= new System.Collections.Specialized.NotifyCollectionChangedEventHandler(sourceList_CollectionChanged);
            }

            if (source == FlyThroughSource.FromCurrentUser)
            {
                sourceList = manager.CurrentFlyThroughData;
                sourceList.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(sourceList_CollectionChanged);
            }
            else
            {
                sourceList = manager.UrlFlyThroughData;
            }

            init();
        }
       
        void init()
        {
            
            segments.Clear();
            filteredData.Clear();
            //loop through each FlyThrough image and add a segment for it
            //to the collection. Segments know how long they are and their angle.

            for (int i = 0; i < sourceList.Count; i++)
            {
                FlyThroughData d = sourceList[i];
                int imageNumber = d.ImageId;

                if (arranger.ImagesToShow.Contains(msi.SubImages[imageNumber]))
                {
                    if (msi.SubImages.Count > imageNumber)
                    {                        
                        filteredData.Add(d);
                    }
                }
            }
            updateSegmentData();
        }

        void updateSegmentData()
        {
            for (int i = 0; i < filteredData.Count; i++)
            {
                FlyThroughData data = filteredData[i];
                int imageNumber = data.ImageId;                

                //Check that the image number does not exceed the possible values
                if (msi.SubImages.Count < imageNumber)
                {
                    continue;
                }

                if (filteredData.Count > i + 1)
                {
                    var segment = getExistingSegment(imageNumber.ToString());                    
                    
                    FlyThroughData nextData = filteredData[i + 1];
                    int nextImageNumber = nextData.ImageId;                    

                    Point myStart = screenCentrePoint(imageNumber);
                    Point myFinish = screenCentrePoint(nextImageNumber);

                    //Calculate the distance and angle of the points
                    
                    double radians = Math.Atan2(myFinish.Y - myStart.Y, myFinish.X - myStart.X);

                    double angle = radians * (180 / Math.PI);
                    
                    double distance = Math.Sqrt(Math.Pow(myFinish.X - myStart.X, 2) + Math.Pow(myFinish.Y - myStart.Y, 2));

                    //update the segment with the new data.
                    //This will propogate to any FlyThroughOverlayItems that are using
                    //this data as FlyThroughData implements INotifyPropertyChanged 
                    segment.Angle = angle;
                    segment.Length = distance;
                    segment.Origin = myStart;
                }
            }
        }

        FlyThroughOverlaySegment getExistingSegment(string id)
        {
            foreach (var s in segments)
            {
                if (s.Id == id)
                {
                    return s;
                }
            }

            var segment = new FlyThroughOverlaySegment() { Id = id };
            
            segments.Add(segment);

            return segment;
        }


        /// <summary>
        /// Calculate the centre point of an image and map it to the real point on the screen. 
        /// This allows the drawing of lines or other templated controls between two images.
        /// </summary>
        /// <param name="imageNumber"></param>
        /// <returns></returns>
        Point screenCentrePoint(int imageNumber)
        {
            MultiScaleSubImage msiSubImg = msi.SubImages[imageNumber];
            Rect subImageScreenRect = arranger.GetSubImageScreenRect(msiSubImg);
            Point actualCentre = new Point((subImageScreenRect.Left + subImageScreenRect.Right) / 2, ((subImageScreenRect.Top + subImageScreenRect.Bottom) / 2));

            return actualCentre;
        }

        public ObservableCollection<FlyThroughOverlaySegment> Segments
        {
            get
            {
                return segments;
            }
        }

        #region EventHandlers

        void msi_ViewportChanged(object sender, RoutedEventArgs e)
        {
            updateSegmentData();
        }       

        void arranger_ArrangeAnimationComplete(object sender, EventArgs e)
        {
            //arrange animation could mean that there was a filter action performed, so the overlay needs to adjust itself
            init();
        }

        void sourceList_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            init();
        }       

        #endregion
    }
}
